import logging
import struct
from typing import Optional

import snap7

# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# PLC连接配置
PLC_IP = "127.0.0.1"  # PLC的IP地址
PLC_RACK = 0  # 机架号
PLC_SLOT = 1  # 插槽号

# 数据块配置
DB_NUMBER = 1  # DB块号
DB_START_OFFSET = 0  # 起始偏移量(字节)


class SiemensReader:
    def __init__(self, ip: str = PLC_IP, rack: int = PLC_RACK, slot: int = PLC_SLOT):
        """
        初始化西门子PLC读取器

        Args:
            ip: PLC的IP地址
            rack: 机架号
            slot: 插槽号
        """
        self.ip = ip
        self.rack = rack
        self.slot = slot
        self.client = snap7.client.Client()
        self.connected = False

    def connect(self) -> bool:
        """
        连接到PLC

        Returns:
            bool: 连接是否成功
        """
        try:
            if not self.connected:
                self.client.connect(self.ip, self.rack, self.slot)
                self.connected = True
                logger.info(f"Successfully connected to PLC at {self.ip}")
            return True
        except Exception as e:
            logger.error(f"Failed to connect to PLC: {str(e)}")
            self.connected = False
            return False

    def disconnect(self):
        """断开与PLC的连接"""
        try:
            if self.connected:
                self.client.disconnect()
                self.connected = False
                logger.info("Disconnected from PLC")
        except Exception as e:
            logger.error(f"Error disconnecting from PLC: {str(e)}")

    def read_lreal(self, db_number: int, start_offset: int) -> Optional[float]:
        """
        读取LREAL (64位浮点数) 数据

        Args:
            db_number: DB块号
            start_offset: 起始偏移量(字节)

        Returns:
            Optional[float]: 读取的LREAL值，如果失败则返回None
        """
        try:
            if not self.connected and not self.connect():
                return None

            # 读取8字节数据(LREAL类型占用8字节)
            data = self.client.db_read(db_number, start_offset, 8)

            # 使用struct解包数据为double(LREAL)
            value = struct.unpack('>d', data)[0]
            # logger.info(f"Successfully read LREAL value: {value} from DB{db_number}.DBX{start_offset}.0")
            return value

        except Exception as e:
            logger.error(f"Error reading LREAL value: {str(e)}")
            return None

    def write_lreal(self, db_number: int, start_offset: int, value: float) -> bool:
        """
        写入LREAL (64位浮点数) 数据

        Args:
            db_number: DB块号
            start_offset: 起始偏移量(字节)
            value: 要写入的浮点数值

        Returns:
            bool: 写入是否成功
        """
        try:
            if not self.connected and not self.connect():
                return False

            # 将浮点数打包为8字节数据
            data = struct.pack('>d', value)

            # 写入数据
            self.client.db_write(db_number, start_offset, data)
            logger.info(f"Successfully wrote LREAL value: {value} to DB{db_number}.DBX{start_offset}.0")
            return True

        except Exception as e:
            logger.error(f"Error writing LREAL value: {str(e)}")
            return False


def main():
    """主函数用于测试"""
    # 创建PLC读取器实例
    plc_reader = SiemensReader()  # 使用默认配置

    try:
        # 连接PLC
        if plc_reader.connect():
            # 写入测试数据
            test_value = 23.456
            if plc_reader.write_lreal(db_number=DB_NUMBER, start_offset=DB_START_OFFSET, value=test_value):
                print(f"Successfully wrote test value: {test_value}")
            else:
                print("Failed to write test value")

            # 读取数据进行验证
            read_value = plc_reader.read_lreal(db_number=DB_NUMBER, start_offset=DB_START_OFFSET)
            if read_value is not None:
                print(f"Read back value: {read_value}")
                if abs(read_value - test_value) < 0.0001:  # 考虑浮点数精度
                    print("Write and read test passed!")
                else:
                    print("Warning: Read value differs from written value!")
            else:
                print("Failed to read value")
    finally:
        # 确保断开连接
        plc_reader.disconnect()


if __name__ == "__main__":
    main() 